﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Linq.Expressions;
using System.Data.Linq.Mapping;
using Lilium.Reflection;
using System.Reflection;
using System.IO;

namespace Lilium.Data.Repository
{
	public class TableAdapter<TEntity> : IRepository<TEntity> where TEntity : class
	{
		public TableAdapter(Table<TEntity> table)
		{
			if (table == null) throw new ArgumentNullException("dataTable");

			m_Table = table;
		}

		#region IRepository<TEntity> Members

		public void Delete(TEntity entity)
		{
			Table.DeleteOnSubmit(entity);
			Table.Context.SubmitChanges();
		}

		public void Insert(TEntity entity)
		{
			Table.InsertOnSubmit(entity);
			Table.Context.SubmitChanges();
		}

		public TEntity Get(object key)
		{
			if (key == null) throw new ArgumentNullException("key");

			var metaType = Table.Context.Mapping.GetMetaType(typeof(TEntity));
			var keyMembers = metaType.DataMembers.Where(m => m.IsPrimaryKey).ToArray();
			var suppliedKeyType = key.GetType();
			var entityParameter = Expression.Parameter(typeof(TEntity), "entity");

			Expression<Func<TEntity, bool>> predicate;

			if (keyMembers.Length == 0)
			{
				throw new InvalidOperationException(string.Format("Key not found on the entity of type {0}", typeof(TEntity)));
			}
			else if ((keyMembers.Length == 1) && (IsAtomic(suppliedKeyType)))
			{
				var atom = Expression.Equal(
					Expression.PropertyOrField(entityParameter, keyMembers[0].Name),
					Expression.Constant(key));

				predicate = Expression.Lambda<Func<TEntity, bool>>(atom, entityParameter);
			}
			else
			{
				throw new NotImplementedException("Выборка по множественному первичному ключу пока не реализована.");
			}

            var entity = Table.SingleOrDefault(predicate);
            return entity;
		}

		public IQueryable<TEntity> GetAll()
		{
			return Table;
		}

		public void Update(TEntity updated)
		{
			Table.Context.Log = new StringWriter();

            try
            {
                var current = Table.First(e => e == updated); // что-то вообще бред какой-то...
                if (current != updated)
                    CopyColumns(updated, current);
                Table.Context.SubmitChanges();
            }
            catch
            {
                throw;
            }
		}

		#endregion

		#region IEnumerable<TEntity> Members

		IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
		{
			return Table.GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return Table.GetEnumerator();
		}

		#endregion

		#region IQueryable Members

		Type IQueryable.ElementType
		{
			get { return ((IQueryable)Table).ElementType; }
		}

		Expression IQueryable.Expression
		{
			get { return ((IQueryable)Table).Expression; }
		}

		IQueryProvider IQueryable.Provider
		{
			get { return ((IQueryable)Table).Provider; }
		}

		#endregion

		private static bool IsAtomic(Type type)
		{
			if (type == null) throw new ArgumentNullException("type");

			return type.IsPrimitive || type.IsValueType;
		}

		private void CopyColumns(TEntity src, TEntity dst)
		{
			var entityType = typeof(TEntity);

			foreach (var pair in from property in entityType.GetProperties()
								 from column in property.GetCustomAttributes<ColumnAttribute>()
								 select new { Property = property, Comumn = column })
			{
                // Если писать в обход свойств, то трекинг не работает. А так это всё дурость, конечно, не нужно перегружать и копировать сущность.
				//if (string.IsNullOrEmpty(pair.Comumn.Storage))
				//{
					var value = pair.Property.GetValue(src, null);
					pair.Property.SetValue(dst, value, null);
				//}
				//else
				//{
				//	var backingField = entityType.GetField(pair.Comumn.Storage, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, true);
				//	var value = backingField.GetValue(src);
				//	backingField.SetValue(dst, value);
				//}
			}
            
		}

		#region private DataContext DataContext

		private Table<TEntity> m_Table;

		private Table<TEntity> Table
		{
			//[System.Diagnostics.DebuggerNonUserCodeAttribute]
			get
			{
				return m_Table;
			}
			//[System.Diagnostics.DebuggerNonUserCodeAttribute]
			set
			{
				m_Table = value;
			}
		}

		#endregion
	}
}
